package cn.singno.bob.jpa.annotation.core;

import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaBuilder.In;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.data.jpa.domain.Specification;

import com.google.common.collect.Lists;

/**
 * <p>名称：DynamicSpecification.java</p>
 * <p>描述：动态查询帮助类。</p>
 * <pre>
 *    现在只能返回整个对象结果
 * </pre>
 * @author 鲍建明
 * @date 2014年8月14日 上午11:22:06
 * @version 1.0.0
 */
public class DynamicSpecification<T> {

	
	/**
	 * 
	 * <p>描述：将带有指定注解的bean对象转换成Predicate(JPA查询对象)</p>
	 * <pre>
	 *    
	 * </pre>
	 * @param <T>
	 * @param bean
	 * @return
	 */
	public static  <T> Specification<T> toPredicate(T bean){
		SearchFilter search = new JPASearchFilter();
		SearchFilter searchFilter = search.parseBean(bean);
		List<SearchFilterBean> searchList = searchFilter.getResult();
		if(CollectionUtils.isEmpty(searchList)){
			return null;
		}
		return new JPASpecificationImpl<T>(searchList, searchFilter.getFields());
	}
	
	/**
	 * 
	 * <p>描述：根据查询对象来查询</p>
	 * <pre>
	 *    
	 * </pre>
	 * @param <T>
	 * @param bean
	 * @return
	 */
	public static  <E> Specification<E> toPredicate(List<SearchFilterBean> searchList, Class<?> clazz){
		return new JPASpecificationImpl<E>(searchList);
	}

	/**
	 * 
	 * <p>描述：根据查询对象来查询</p>
	 * <pre>
	 *    
	 * </pre>
	 * @param <T>
	 * @param bean
	 * @return
	 */
	public static  <E> Specification<E> toPredicate(SearchFilterBean search, Class<?> clazz){
		if(search == null){
			return null;
		}
		return new JPASpecificationImpl<E>(Lists.newArrayList(search));
	}
	
	
	
	/**
	 * <p>名称：JPASpecificationImpl.java</p>
	 * <p>描述：Specification<T> 实现类</p>
	 * <pre>
	 *    
	 * </pre>
	 * @author 鲍建明
	 * @date 2014年8月14日 下午12:39:05
	 * @version 1.0.0
	 */
	static class JPASpecificationImpl<T> implements Specification<T>{

		/**
		 * 条件对象
		 */
		private List<SearchFilterBean> searchFilterBean;
		
		/**
		 * 需要被返回的对象
		 * 该字段为预留。现在实现不了。以后看情况
		 */
		private List<Object> fields;
		
		private Root<T> root;
		
		private CriteriaQuery<?> query;
		
		private CriteriaBuilder builder;
		
		/**
		 * 该字段为扩展，原生他支持排序和分页，
		 * 如果使用注解的形式也可以
		 */
		private List<Order> orders = Lists.newLinkedList();
		
		public JPASpecificationImpl(List<SearchFilterBean> searchFilterBean, List<Object> fields){
			this.searchFilterBean = searchFilterBean;
			this.fields = fields;
		}
		public JPASpecificationImpl(List<SearchFilterBean> searchFilterBean){
			this.searchFilterBean = searchFilterBean;
		}
		
		
		/**
		 * 
		 * <p>描述：设置基本参数</p>
		 * <pre>
		 *    
		 * </pre>
		 * @param root
		 * @param query
		 * @param builder
		 */
		private void setSpecificationParamer(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder){
			this.root = root;
			this.query = query;
			this.builder = builder;
		}
		
		
		/**
		 * 
		 * <p>描述：添加排序</p>
		 * <pre>
		 *    
		 * </pre>
		 * @param order
		 */
		private JPASpecificationImpl<T> addOrder(){
			if(CollectionUtils.isNotEmpty(this.orders)){
				this.query.orderBy(this.orders);
			}
			return this;
		}
		
		/**
		 * 
		 * <p>描述：设置返回参数字段</p>
		 * <pre>
		 *    
		 * </pre>
		 * @return
		 */
		private JPASpecificationImpl<T> addSelect(){
			List<Selection<?>> selectionList = Lists.newArrayList();
			if (CollectionUtils.isNotEmpty(fields)) {
				for (Object field : fields) {
					Selection<?> selection = this.root.get(field.toString()).alias(field.toString());
					selectionList.add(selection);
				}
			}
			this.query.multiselect(selectionList);
			return this;
		}
		
		/**
		 * 
		 * <p>描述：拼接最后的结果返回</p>
		 * <pre>
		 *    
		 * </pre>
		 * @param predicates
		 * @return
		 */
		private Predicate returnPredicate(List<Predicate> predicates){
			if(CollectionUtils.isNotEmpty(predicates)){					//有条件的
				return this.builder.and(predicates.toArray(new Predicate[]{}));
			}
			return this.builder.conjunction();								//没有条件的
		}

	
		/**
		 * 主体
		 */
		@Override
		public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
			setSpecificationParamer(root, query, builder);
			List<Predicate> predicates = Lists.newArrayList();
			for (SearchFilterBean searchBean : searchFilterBean) {
				setParamer(searchBean, root, query, builder, predicates);
			}
			return addOrder().addSelect().returnPredicate(predicates);
		}
		
		
		/**
		 * 
		 * <p>描述：设置查询参数</p>
		 * <pre>
		 *    
		 * </pre>
		 * @param searchBean
		 * @param root
		 * @param query
		 * @param builder
		 * @param predicates
		 */
		@SuppressWarnings({ "unchecked", "rawtypes" })
		private void setParamer(SearchFilterBean searchBean, Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder, List<Predicate> predicates ){
			// 在查询的时候，在字段名称前面加个表别名, 如Task的名为"user.name"的filedName, 转换为Task.user.name属性
			Path path = root.get(searchBean.getFieldName());
			switch (searchBean.getOperator()) {
			case EQ:
				predicates.add(builder.equal(path, searchBean.getValues().get(0)));
				break;
			case LIKE:
				predicates.add(builder.like(path, "%" + searchBean.getValues().get(0) + "%")) ;
				break;
			case GREATER:
				predicates.add(builder.greaterThan( path, (Comparable)searchBean.getValues().get(0) ));
				break;
			case GREATER_EQ:
				predicates.add(builder.greaterThanOrEqualTo(path, (Comparable) searchBean.getValues().get(0) ));
				break;
			case IN:
				In<String> in = builder.in(path);
				for (Object obj : searchBean.getValues())  in.value(obj.toString());
				predicates.add(in);
				break;
			case LESS:
				predicates.add(builder.lessThan(path, (Comparable) searchBean.getValues().get(0) ));
				break;
			case LESS_EQ:
				predicates.add(builder.lessThanOrEqualTo(path, (Comparable) searchBean.getValues().get(0) ));
				break;
			case BETWEEN_AND:
				predicates.add( builder.between(path, (Comparable)searchBean.getValues().get(0), (Comparable)searchBean.getValues().get(1) ) );
				break;
			case ORDERBY:
				String orderStr = searchBean.getValues().get(0).toString();
				if("desc".equalsIgnoreCase(orderStr))    this.orders.add( builder.desc(path) );
				else if("asc".equalsIgnoreCase(orderStr))  this.orders.add( builder.asc(path) );
				break;
			default:
				break;
			}
		}
		
		
	}
	
}
